﻿using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Security.Credentials;
using Windows.Storage.Streams;
using Windows.Web.Http;
using Windows.Web.Http.Filters;

namespace Microsoft.Media.IngestClient
{
    /// <summary>
    /// Implementation of IPushOutputConsumer that can stream the output from PushClient to an http destination.
    /// </summary>
    public sealed class HttpPushOutputConsumer : IPushOutputConsumer, IDisposable
    {
        const string UserAgent = "IngestClient";

        HttpClient httpClient;
        HttpBaseProtocolFilter httpProtocolFilter;
        CancellationTokenSource cts;
        Task<HttpResponseMessage> currentTask;

        /// <summary>
        /// Creates a new instance of HttpPushOutputConsumer.
        /// </summary>
        public HttpPushOutputConsumer()
        {
            StreamIdentifier = "Encoder1";
        }

        /// <summary>
        /// Gets or sets the channel ingest URL to send data to.
        /// </summary>
        public Uri IngestionUrl { get; set; }

        /// <summary>
        /// Gets or sets the name of the stream used when sending data to the the ingest service. This can be set to anything.
        /// </summary>
        public string StreamIdentifier { get; set; }

        /// <summary>
        /// Gets the most recent Http response status code.
        /// </summary>
        public HttpStatusCode HttpResponseStatusCode { get; private set; }

        /// <summary>
        /// Initializes the session by connecting to the ingest server. Use this to endpoint will accept data before sending data.
        /// </summary>
        /// <returns>An awaitable action.</returns>
        public IAsyncAction InitializeAsync()
        {
            return AsyncInfo.Run((CancellationToken c) => InitializeAsync(c));
        }

        internal async Task InitializeAsync(CancellationToken cancellationToken)
        {
            HttpResponseStatusCode = HttpStatusCode.None;
            cts = new CancellationTokenSource();
            try
            {
                httpProtocolFilter = new HttpBaseProtocolFilter()
                {
                    AutomaticDecompression = false,
                    AllowUI = false
                };
                httpClient = new HttpClient(httpProtocolFilter);
                try
                {
                    httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);

                    // Blank POST
                    IBuffer emptyBuffer = (new byte[0]).AsBuffer();
                    using (var c = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
                    {
                        var response = await (currentTask = httpClient.PostAsync(IngestionUrl, new HttpBufferContent(emptyBuffer)).AsTask(c.Token));
                        HttpResponseStatusCode = response.StatusCode;
                        response.EnsureSuccessStatusCode();
                    }
                }
                catch
                {
                    httpProtocolFilter.Dispose();
                    httpProtocolFilter = null;
                    currentTask = null;
                    httpClient.Dispose();
                    httpClient = null;
                    throw;
                }
            }
            catch
            {
                cts.Dispose();
                cts = null;
                throw;
            }
        }

        /// <inheritdoc />
        public IAsyncActionWithProgress<ulong> SendDataAsync(IInputStream inputStream)
        {
            return AsyncInfo.Run((CancellationToken c, IProgress<ulong> p) => SendDataAsync(inputStream, p, c));
        }

        internal async Task SendDataAsync(IInputStream inputStream, IProgress<ulong> progress, CancellationToken cancellationToken)
        {
            HttpResponseStatusCode = HttpStatusCode.None;
            Uri encoderEndpoint = !string.IsNullOrEmpty(StreamIdentifier) ? new Uri(IngestionUrl, string.Format("{0}/Streams({1})", IngestionUrl.LocalPath, StreamIdentifier)) : IngestionUrl;

            using (var httpContent = new HttpStreamContent(inputStream))
            {
                EventHandler<HttpProgress> progressChangedHandler = (s, e) =>
                {
                    progress.Report(e.BytesSent);
                };

                var httpProgress = new Progress<HttpProgress>();
                httpProgress.ProgressChanged += progressChangedHandler;

                try
                {
                    using (var c = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
                    {
                        //var request = new HttpRequestMessage(HttpMethod.Post, encoderEndpoint);
                        //request.Content = httpContent;
                        //await httpClient.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead);

                        var response = await (currentTask = httpClient.PostAsync(encoderEndpoint, httpContent).AsTask(c.Token, httpProgress));
                        HttpResponseStatusCode = response.StatusCode;
                        response.EnsureSuccessStatusCode();
                    }
                }
                catch
                {
                    currentTask = null;
                    throw;
                }
                finally
                {
                    httpProgress.ProgressChanged -= progressChangedHandler;
                }
            }
        }

        /// <inheritdoc />
        public IAsyncAction TerminateAsync()
        {
            return _TerminateAsync().AsAsyncAction();
        }

        async Task _TerminateAsync()
        {
            cts.Cancel();
            if (currentTask != null)
            {
                try
                {
                    await currentTask;
                }
                catch { /* ignore, we just want to make sure everything is unwired before continuing. */ }
                currentTask = null;
            }
            cts.Dispose();
            cts = null;
            httpProtocolFilter.Dispose();
            httpProtocolFilter = null;
            httpClient.Dispose();
            httpClient = null;
        }

        /// <inheritdoc />
        public void Dispose()
        {
            var noawait = _TerminateAsync();
        }
    }
}
